package com.zx.sdk;

import com.zx.sdk.league.member.BaseLeagueMember;
import com.zx.sdk.model.AdInfo;
import com.zx.sdk.util.ConfigHelper;
import com.zx.sdk.util.LogHelper;
import com.zx.sdk.util.SPHelper;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;

/**
 * 策略帮助类
 * <p>
 * 我们引入 **冻结系数**  的概念（类似于 TCP 的慢启动），time=time*系数
 * 如果系数是 2 间隔会是如下
 * 1,2,4,8,16,32,64,128,256,512,1024,2048
 * 如果系数是 5
 * 1,5,25,125,625,3125
 * 咱们优化下【高价过滤】的策略，以前是1分钟内不会再次询问，我们现在把 1分钟 改成 动态的分钟数。比如：
 * 背景：已知设备A有预加载广告位 c88，上一次失败的时间是 noFillTime，冻结请求的冷却时间 frozenTime 是2分钟，冻结系数是 2
 * 流程说明：
 * ```
 * 设备A在预加载广告位c88前（是否过滤请求）判断价差和请求冻结情况，
 * * 如果当前时间小于noFillTime+frozenTime（还没解冻）就不请求，否则请求预加载，
 * 若这一次请求预加载还是无填充，那么就把当前时间覆盖 noFillTime，frozenTime=frozenTime*系数
 * 这一次预加载有填充，清空 noFillTime，frozenTime
 * ```
 * 流程图：
 * <p>
 * ```flow
 * 开始=>start: 开始预加载广告位 c88
 * 结束=>end: 结束预加载流程
 * 是否过滤请求=>condition: 判断是否过滤请求，判断价差和请求冻
 * （当前时间小于 noFillTime 加 frozenTime（还没解冻）
 * 是否有填充=>condition: 广告位是否有填充
 * 无填充=>operation: 1. 用当前时间覆盖noFillTime,
 * 2. frozenTime乘以冻结系数后覆盖frozenTime
 * （freezeTime=freezeTimex2）,
 * 有填充=>operation: 清空noFillTime,清空frozenTime
 * <p>
 * <p>
 * 开始->是否过滤请求(yes,right)->结束
 * 开始->是否过滤请求(no)->是否有填充(yes)->有填充->结束
 * 开始->是否过滤请求(no)->是否有填充(no,left)->无填充->结束
 * ```
 */
public class StrategyHelper {

    /**
     * 最近的一次竞胜价
     */
    public static final String LAST_WIN_PRICE = "last_win_price_";

    /**
     * 广告位最后一次加载失败的时间
     */
    public static final String LAST_LOAD_ERROR_TIME = "last_load_error_time_";

    /**
     * 广告位冻结请求 的冻结分钟数
     */
    public static final String Pid_Frozen_Sec = "pid_frozen_sec_";

    public static long getLastUnFillTime(String appId, String pid) {
        return (long) SPHelper.get(LAST_LOAD_ERROR_TIME + appId + pid, 0L);
    }

    public static int getFrozenSec(String appId, String pid) {
        return (int) SPHelper.get(Pid_Frozen_Sec + appId + pid, 0);
    }

    /**
     * 有填充
     */
    public static void onFilledSuc(String appId, String pid) {
        // 预加载有填充，清空 noFillTime，frozenTime
        SPHelper.remove(LAST_LOAD_ERROR_TIME + appId + pid);
        SPHelper.remove(Pid_Frozen_Sec + appId + pid);
    }

    /**
     * 未填充
     */
    public static void onFillError(String appId, String pid) {
        SPHelper.put(LAST_LOAD_ERROR_TIME + appId + pid, System.currentTimeMillis());
        int curFrozenSec = getFrozenSec(appId, pid);
        // 没有被冻结过 就写入 pidIgnoreMin
        if (curFrozenSec < 1) {
            LogHelper.d("appId", appId, "pid", pid, "之前没冻结过，冻结初始时间" + ConfigHelper.getFrozenSec());
            SPHelper.put(Pid_Frozen_Sec + appId + pid, ConfigHelper.getFrozenSec());
            return;
        }
        // 这一次请求预加载还是无填充，那么就把当前时间覆盖 noFillTime，frozenTime=frozenTime*系数
        int newFrozenSec = Float.valueOf(curFrozenSec * ConfigHelper.getFrozenFactor()).intValue();
        // 如果 冻结秒大于【最大冻结时长】就重置冻结时间，重新开始 慢启动冻结
        if (newFrozenSec > ConfigHelper.getMaxFrozenHour() * 3600) {
            SPHelper.remove(LAST_LOAD_ERROR_TIME + appId + pid);
            SPHelper.remove(Pid_Frozen_Sec + appId + pid);
            LogHelper.d("appId", appId, "pid", pid, "大于最大冻结", String.valueOf(ConfigHelper.getMaxFrozenHour() * 3600), "，直接解冻 " + newFrozenSec);
            return;
        }
        LogHelper.d("appId", appId, "pid", pid, "刷新 newFrozenSec " + getFrozenSec(appId, pid), String.valueOf(ConfigHelper.getFrozenFactor()), String.valueOf(newFrozenSec));
        SPHelper.put(Pid_Frozen_Sec + appId + pid, newFrozenSec);
    }

    /**
     * 是否过滤当前预加载请求
     * 1. 短期内已失败的不再高频请求
     * 2. 已缓存的不再请求
     */
    public static boolean isFilterPreload(String type, AdInfo adInfo, BaseLeagueMember<?, ?, ?, ?,?> member) {
        // 一段时间内价格过高
        String mapPid = adInfo.getMapPid();
        String mapAppId = adInfo.getMapAppid();
        if (isPriceTooHigh(type, adInfo.getCpm() * 100, member, mapAppId, mapPid)
                && isFrozen(member, mapAppId, mapPid)) {
            LogHelper.d("联盟=" + member.getName(), "上次加载" + type + "无填充并且价格过高，冻结预加载 appId = " + mapAppId + " pid =" + mapPid);
            return true;
        }
        // 缓存还有效
        return member.isPreResReady(type, mapPid);
    }

    /**
     * 保存最后一次竞胜价
     */
    public static void saveLastWinPrice(String type, int cpm, String pid) {
        LogHelper.d("显示" + type + "广告成功 价格 = " + cpm + " pid = " + pid);
        SPHelper.put(LAST_WIN_PRICE + type, cpm);
    }

    public static String getLastWinPrice(String type) {
        Object o = SPHelper.get(LAST_WIN_PRICE + type, "0");
        return o == null ? "" : o.toString();
    }

    /**
     * 广告位是否因为价格过高而导致本次禁止加载
     */
    public static boolean isPriceTooHigh(String type, int price, BaseLeagueMember<?, ?, ?, ?,?> member, String appId, String pid) {
        int lastWinPrice = (int) SPHelper.get(LAST_WIN_PRICE + type, -1);
        if (lastWinPrice == -1) { // 说明没有记录过成功的价格，可以预加载。
            return false;
        }
        int ignorePrice = 0;
        switch (type) {
            case ZxSDK.SPLASH:
                ignorePrice = ConfigHelper.getSplashPidIgnorePrice();
                break;
            case ZxSDK.REWARD:
                ignorePrice = ConfigHelper.getRewardPidIgnorePrice();
                break;
        }
        boolean tooHigh = price - lastWinPrice > ignorePrice;
        if (tooHigh) {
            LogHelper.d("联盟=" + member.getName(), "加载" + type + "广告价格太高 = " + price
                    + " 最近胜出价=" + lastWinPrice
                    + " appId = " + appId + " pid =" + pid);
        }
        return tooHigh;
    }

    /**
     * 上一次失败的时间是 noFillTime
     * 冻结请求的冷却时间 frozenTime
     * 设备A在预加载广告位c88前（是否过滤请求）判断价差和请求冻结情况，
     * 如果当前时间小于 noFillTime+frozenTime（还没解冻）就不请求，否则请求预加载，
     * 若这一次请求预加载还是无填充，那么就把当前时间覆盖 noFillTime，frozenTime=frozenTime*系数
     */
    private static boolean isFrozen(BaseLeagueMember<?, ?, ?, ?,?> member, String appId, String pid) {
        long lastLoadFailTime = getLastUnFillTime(appId, pid);
        int frozenSec = getFrozenSec(appId, pid);
        // 解冻（可以进行预加载）的时间 = 最后失败的时间 + 冻结的时间
        long unFreezeTime = lastLoadFailTime + frozenSec * 1000L;
        boolean isFilterReq = System.currentTimeMillis() < unFreezeTime;
        LogHelper.d("判断 isFrozen" + pid, String.valueOf(isFilterReq));

        // 当前时间已经过了解冻时间
        if (isFilterReq) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);

            LogHelper.d("请求应冻结 " + pid,
                    "上次无填充 " + member.getName(), appId,
                    "最近失败时间:" + simpleDateFormat.format(new Date(lastLoadFailTime)),
                    "解冻时间:" + simpleDateFormat.format(new Date(unFreezeTime))
            );
        }
        return isFilterReq;
    }

    /**
     * 打印状态
     */
    public static void printState(ArrayList<AdInfo> list) {
        for (AdInfo item : list) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);

            String appId = item.getMapAppid();
            String pid = item.getMapPid();
            long lastLoadFailTime = getLastUnFillTime(appId, pid);
            int frozenSec = getFrozenSec(appId, pid);
            long unFreezeTime = lastLoadFailTime + frozenSec * 1000L;
            String formatLastFail = "最近失败:" + simpleDateFormat.format(lastLoadFailTime);
            String formatFrozen = "解冻:" + simpleDateFormat.format(unFreezeTime);
            if (lastLoadFailTime == 0) {
                formatLastFail = "最近有填充";
            }
            if (unFreezeTime == 0) {
                formatFrozen = "未冻结";
            }

            LogHelper.d(item.getLeague(), appId, pid, String.valueOf(item.getCpm()),
                    formatLastFail, formatFrozen
            );
        }
    }
}
